package cz.matasek.dbLogger.model;

import cz.matasek.dbLogger.LoggingAvailability;
import cz.matasek.dbLogger.database.DatabaseConnection;
import cz.matasek.dbLogger.exception.LoggingUnavailableException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
 * The class represents a database.
 *
 * @author Luboš Matásek
 */
public class Database {

    /**
     * Connection to the database.
     */
    private DatabaseConnection databaseConnection;
    /**
     * Mapping of table names to table objects. Contains all tables present
     * in the database.
     */
    private Map<String, Table> tables;
    /**
     * Logging availability of this database.
     */
    private LoggingAvailability loggingAvailability;

    /**
     * Creates a new <code>Database</code> object.
     *
     * @param databaseConnection the connection to the database
     */
    public Database(DatabaseConnection databaseConnection) {
        this.databaseConnection = databaseConnection;
        this.tables = new HashMap<String, Table>();

        updateMetadata();
    }

    /**
     * Updates the database metadata. It means the following:
     * <ul>
     *  <li>list of tables present in the database is updated</li>
     *  <li>existence of table changes logging is checked</li>
     *  <li>existence of the history table is checked</li>
     * </ul>
     */
    public void updateMetadata() {
        loggingAvailability = databaseConnection.getLoggingAvailability();
        tables.clear();


        // creates new table objects
        for (String tableName : databaseConnection.getTables()) {
            Table table = new Table(
                    this, tableName, databaseConnection.getColumns(tableName));
            table.setLogged(false);
            table.setHistoryTable(false);

            tables.put(tableName, table);
        }


        // if logging is available in the database, sets additional metadata
        if (loggingAvailability == LoggingAvailability.AVAILABLE) {
            for (Table table : tables.values()) {
                table.setLogged(
                        databaseConnection.hasLoggingTrigger(table.getName()));
                table.setHistoryTable(
                        databaseConnection.hasHistoryTable(table.getName()));

                if (table.isLogged()) {
                    table.setLoggedEvents(
                            databaseConnection.getTriggerEvents(table.getName()));
                    table.setLoggedColumns(
                            databaseConnection.getLoggedColumns(table.getName()));
                }
            }
        }
    }

    /**
     * Returns the tables present in the database.
     *
     * @return the collection of tables present in the database
     */
    public Collection<Table> getTables() {
        return tables.values();
    }

    /**
     * Returns the user names present in the database.
     *
     * @return the collection of user names present in the database
     */
    public Collection<String> getUsers() {
        return databaseConnection.getUsers();
    }

    /**
     * Returns table of the specified name (if exists).
     *
     * @param tableName name of the table
     * @return table of the specified name or <code>null</code> if table of such
     * a name does not exist
     */
    public Table getTable(String tableName) {
        return tables.get(tableName);
    }

    /**
     * Returns tables, which have the history table.
     *
     * @return the collection of tables with the history tables
     */
    public Collection<Table> getTablesWithHistory() {
        HashSet<Table> tablesWithHistory = new HashSet<Table>();

        for (Table table : tables.values()) {
            if (table.hasHistoryTable()) {
                tablesWithHistory.add(table);
            }
        }

        return tablesWithHistory;
    }

    /**
     * Returns tables, which were not auto-generated by the logging application.
     *
     * @return the collection of tables, were not auto-generated by the logging
     * application
     */
    public Collection<Table> getNonLoggingTables() {
        if (loggingAvailability == LoggingAvailability.UNAVAILABLE) {
            return getTables();
        }

        HashSet<Table> nonLoggingTables = new HashSet<Table>();
        String loggingPrefix = databaseConnection.getPrefix();

        for (Table table : tables.values()) {
            if (!table.isLoggingTable(loggingPrefix)) {
                nonLoggingTables.add(table);
            }
        }

        return nonLoggingTables;
    }

    /**
     * Returns logging availability of this database.
     *
     * @return the logging availability of this database
     */
    public LoggingAvailability getLoggingAvailability() {
        return loggingAvailability;
    }

    /**
     * Returns <code>true</code> if logging is available in this database.
     *
     * @return <code>true</code> if logging is available in this database;
     * <code>false</code> otherwise
     */
    public boolean isLoggingAvailable() {
        if (loggingAvailability == LoggingAvailability.AVAILABLE) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns prefix of all logging objects or <code>null</code> if logging is
     * not available.
     *
     * @return logging prefix or <code>null</code> if logging is not available
     */
    public String getLoggingPrefix() {
        if (loggingAvailability == LoggingAvailability.AVAILABLE) {
            return databaseConnection.getPrefix();
        } else {
            return null;
        }
    }

    /**
     * Returns current database connection.
     *
     * @return the database connection
     */
    public DatabaseConnection getConnection() {
        return databaseConnection;
    }

    /**
     * Returns <code>true</code> if the database has the logging table.
     *
     * @return <code>true</code> if the database has the logging table;
     * <code>false</code> otherwise
     */
    public boolean hasLoggingTable() {
        try {
            return databaseConnection.hasLoggingTable();
        } catch (LoggingUnavailableException ex) {
            return false;
        }
    }
}
